libobs_wrapper\data\output/
traits.rs

1use std::{
2    collections::HashMap,
3    ffi::CStr,
4    fmt::Debug,
5    sync::{Arc, RwLock},
6};
7
8use crate::{
9    data::object::ObsObjectTrait,
10    encoders::{audio::ObsAudioEncoder, video::ObsVideoEncoder},
11    enums::ObsOutputStopSignal,
12    macros::trait_with_optional_send_sync,
13    run_with_obs,
14    runtime::ObsRuntime,
15    utils::{AudioEncoderInfo, ObsError, OutputInfo, VideoEncoderInfo},
16};
17
18use super::ObsOutputSignals;
19
20trait_with_optional_send_sync! {
21    pub(crate) trait ObsOutputTraitSealed: Debug {
22        /// Creates a new output reference from the given output info and runtime.
23        ///
24        /// # Arguments
25        /// * `output` - The output information containing ID, name, and optional settings
26        /// * `runtime` - The OBS runtime instance
27        ///
28        /// # Returns
29        /// A Result containing the new ObsOutputRef or an error
30        fn new(output: OutputInfo, runtime: ObsRuntime) -> Result<Self, ObsError>
31        where
32            Self: Sized;
33    }
34}
35
36#[allow(private_bounds)]
37pub trait ObsOutputTrait: ObsOutputTraitSealed + ObsObjectTrait<*mut libobs::obs_output_t> {
38    fn signals(&self) -> &Arc<ObsOutputSignals>;
39
40    fn video_encoder(&self) -> &Arc<RwLock<Option<Arc<ObsVideoEncoder>>>>;
41    fn audio_encoders(&self) -> &Arc<RwLock<HashMap<usize, Arc<ObsAudioEncoder>>>>;
42
43    /// Returns the current video encoder attached to this output, if any.
44    fn get_current_video_encoder(&self) -> Result<Option<Arc<ObsVideoEncoder>>, ObsError> {
45        let curr = self
46            .video_encoder()
47            .read()
48            .map_err(|e| ObsError::LockError(e.to_string()))?;
49
50        Ok(curr.clone())
51    }
52
53    /// Creates and attaches a new video encoder to this output.
54    ///
55    /// Fails if the output is active.
56    fn create_and_set_video_encoder(
57        &mut self,
58        info: VideoEncoderInfo,
59    ) -> Result<Arc<ObsVideoEncoder>, ObsError> {
60        if self.is_active()? {
61            return Err(ObsError::OutputAlreadyActive);
62        }
63
64        let video_enc = ObsVideoEncoder::new_from_info(info, self.runtime().clone())?;
65
66        self.set_video_encoder(video_enc.clone())?;
67        Ok(video_enc)
68    }
69
70    /// Attaches an existing video encoder to this output.
71    ///
72    /// Fails if the output is active.
73    fn set_video_encoder(&mut self, encoder: Arc<ObsVideoEncoder>) -> Result<(), ObsError> {
74        if self.is_active()? {
75            return Err(ObsError::OutputAlreadyActive);
76        }
77
78        let output_ptr = self.as_ptr();
79        let encoder_ptr = encoder.as_ptr();
80        let runtime = self.runtime().clone();
81
82        run_with_obs!(runtime, (output_ptr, encoder_ptr), move || {
83            unsafe {
84                // Safety: This is safe because we are only using smart pointers.
85                libobs::obs_output_set_video_encoder(output_ptr.get_ptr(), encoder_ptr.get_ptr());
86            }
87        })?;
88
89        self.video_encoder()
90            .write()
91            .map_err(|e| ObsError::LockError(e.to_string()))?
92            .replace(encoder);
93
94        Ok(())
95    }
96
97    /// Creates and attaches a new audio encoder for the given mixer index. Fails if output active.
98    fn create_and_set_audio_encoder(
99        &mut self,
100        info: AudioEncoderInfo,
101        mixer_idx: usize,
102    ) -> Result<Arc<ObsAudioEncoder>, ObsError> {
103        if self.is_active()? {
104            return Err(ObsError::OutputAlreadyActive);
105        }
106
107        let audio_enc = ObsAudioEncoder::new_from_info(info, mixer_idx, self.runtime().clone())?;
108        self.set_audio_encoder(audio_enc.clone(), mixer_idx)?;
109        Ok(audio_enc)
110    }
111
112    /// Attaches an existing audio encoder to this output at the mixer index.
113    ///
114    /// Fails if the output is active.
115    fn set_audio_encoder(
116        &mut self,
117        encoder: Arc<ObsAudioEncoder>,
118        mixer_idx: usize,
119    ) -> Result<(), ObsError> {
120        if self.is_active()? {
121            return Err(ObsError::OutputAlreadyActive);
122        }
123
124        let encoder_ptr = encoder.as_ptr();
125        let output_ptr = self.as_ptr();
126        let runtime = self.runtime().clone();
127        run_with_obs!(runtime, (output_ptr, encoder_ptr), move || {
128            unsafe {
129                // Safety: This is safe because we are only using smart pointers.
130                libobs::obs_output_set_audio_encoder(
131                    output_ptr.get_ptr(),
132                    encoder_ptr.get_ptr(),
133                    mixer_idx,
134                );
135            }
136        })?;
137
138        self.audio_encoders()
139            .write()
140            .map_err(|e| ObsError::LockError(e.to_string()))?
141            .insert(mixer_idx, encoder);
142
143        Ok(())
144    }
145
146    /// Starts the output, wiring encoders to global contexts and invoking obs_output_start.
147    /// Returns an error with last OBS message when start fails.
148    fn start(&self) -> Result<(), ObsError> {
149        if self.is_active()? {
150            return Err(ObsError::OutputAlreadyActive);
151        }
152
153        let vid_encoder_ptr = self
154            .video_encoder()
155            .read()
156            .map_err(|e| ObsError::LockError(e.to_string()))?
157            .as_ref()
158            .map(|enc| enc.as_ptr());
159
160        let audio_encoder_pointers = self
161            .audio_encoders()
162            .read()
163            .map_err(|e| ObsError::LockError(e.to_string()))?
164            .values()
165            .map(|enc| enc.as_ptr())
166            .collect::<Vec<_>>();
167
168        let output_ptr = self.as_ptr();
169        let runtime = self.runtime().clone();
170        let res = run_with_obs!(
171            runtime,
172            (output_ptr, vid_encoder_ptr, audio_encoder_pointers),
173            move || {
174                if let Some(vid_encoder_ptr) = vid_encoder_ptr {
175                    unsafe {
176                        // Safety: vid_encoder_ptr is valid because of SmartPointer
177                        libobs::obs_encoder_set_video(
178                            vid_encoder_ptr.get_ptr(),
179                            libobs::obs_get_video(),
180                        );
181                    }
182                }
183                for audio_encoder_ptr in audio_encoder_pointers {
184                    unsafe {
185                        // Safety: audio_encoder_ptr is valid because of SmartPointer
186                        libobs::obs_encoder_set_audio(
187                            audio_encoder_ptr.get_ptr(),
188                            libobs::obs_get_audio(),
189                        );
190                    }
191                }
192
193                unsafe {
194                    // Safety: output_ptr is valid because of SmartPointer
195                    libobs::obs_output_start(output_ptr.get_ptr())
196                }
197            }
198        )?;
199
200        if res {
201            return Ok(());
202        }
203
204        let runtime = self.runtime().clone();
205        let err = run_with_obs!(runtime, (output_ptr), move || {
206            let err = unsafe {
207                // Safety: The output pointer must be valid because of SmartPointer
208                libobs::obs_output_get_last_error(output_ptr.get_ptr())
209            };
210
211            if err.is_null() {
212                return "Unknown error".to_string();
213            }
214
215            let err = unsafe { CStr::from_ptr(err) };
216
217            let err = err.to_string_lossy().to_string();
218            err
219        })?;
220
221        Err(ObsError::OutputStartFailure(Some(err)))
222    }
223
224    fn set_paused(&self, should_pause: bool) -> Result<(), ObsError> {
225        if !self.is_active()? {
226            return Err(ObsError::OutputPauseFailure(Some(
227                "Output is not active.".to_string(),
228            )));
229        }
230
231        let output_ptr = self.as_ptr();
232        let runtime = self.runtime().clone();
233
234        let mut rx = if should_pause {
235            self.signals().on_pause()?
236        } else {
237            self.signals().on_unpause()?
238        };
239
240        let res = run_with_obs!(runtime, (output_ptr), move || {
241            unsafe {
242                // Safety: output_ptr is valid because of SmartPointer
243                libobs::obs_output_pause(output_ptr.get_ptr(), should_pause)
244            }
245        })?;
246
247        if res {
248            rx.blocking_recv().map_err(|_| ObsError::NoSenderError)?;
249
250            Ok(())
251        } else {
252            let runtime = self.runtime().clone();
253            let err = run_with_obs!(runtime, (output_ptr), move || {
254                let err = unsafe {
255                    // Safety: output_ptr is valid because of SmartPointer
256                    libobs::obs_output_get_last_error(output_ptr.get_ptr())
257                };
258
259                if err.is_null() {
260                    return None;
261                }
262
263                let err = unsafe { CStr::from_ptr(err) };
264                let err = err.to_string_lossy().to_string();
265
266                Some(err)
267            })?;
268
269            Err(ObsError::OutputPauseFailure(err))
270        }
271    }
272
273    /// Pauses or resumes the output and waits for the pause/unpause signal.
274    fn pause(&self) -> Result<(), ObsError> {
275        self.set_paused(true)
276    }
277
278    fn unpause(&self) -> Result<(), ObsError> {
279        self.set_paused(false)
280    }
281
282    /// Stops the output and waits for stop and deactivate signals.
283    fn stop(&mut self) -> Result<(), ObsError> {
284        let output_ptr = self.as_ptr();
285        let runtime = self.runtime().clone();
286        let output_active = run_with_obs!(runtime, (output_ptr), move || {
287            unsafe {
288                // Safety: output_ptr is valid because of SmartPointer
289                libobs::obs_output_active(output_ptr.get_ptr())
290            }
291        })?;
292
293        if !output_active {
294            return Err(ObsError::OutputStopFailure(Some(
295                "Output is not active.".to_string(),
296            )));
297        }
298
299        let mut rx = self.signals().on_stop()?;
300        let mut rx_deactivate = self.signals().on_deactivate()?;
301
302        let runtime = self.runtime().clone();
303        run_with_obs!(runtime, (output_ptr), move || {
304            unsafe {
305                // Safety: output_ptr is valid because of SmartPointer
306                libobs::obs_output_stop(output_ptr.get_ptr())
307            }
308        })?;
309
310        let signal = rx.blocking_recv().map_err(|_| ObsError::NoSenderError)?;
311
312        log::trace!("Received stop signal: {:?}", signal);
313        if signal != ObsOutputStopSignal::Success {
314            return Err(ObsError::OutputStopFailure(Some(signal.to_string())));
315        }
316
317        rx_deactivate
318            .blocking_recv()
319            .map_err(|_| ObsError::NoSenderError)?;
320
321        Ok(())
322    }
323
324    /// Returns whether the output is currently active.
325    fn is_active(&self) -> Result<bool, ObsError> {
326        let output_ptr = self.as_ptr();
327        let runtime = self.runtime().clone();
328        let output_active = run_with_obs!(runtime, (output_ptr), move || {
329            unsafe {
330                // Safety: output_ptr is valid because of SmartPointer
331                libobs::obs_output_active(output_ptr.get_ptr())
332            }
333        })?;
334
335        Ok(output_active)
336    }
337}